#!/usr/bin/env ruby

require 'DescentObserver'

class Map
  
  attr_accessor :witdh, :height, :grid
  
  def initialize(width, height)
    @width = width
    @height = height
    @grid = []
   
    for i in 0...width
      @grid[i] = []
      
      for j in 0...height
        @grid[i][j] = Grid.new(Location.new(i,j), Floor.new, nil, nil)
      end
    end
  end
  
  def get_grid(loc)
    if (@grid[loc.point_x] != nil) then
      return @grid[loc.point_x][loc.point_y]
    end
  end
  
  def set_grid(loc, grid)
    @grid[loc.point_x][loc.point_y] = grid
  end
  
  def add_structure(loc, structure)
    get_grid(loc).structure = structure
    #structure.register_touch_listener(get_grid(loc))
  end
  
  def add_entity(loc, entity)
    get_grid(loc).entity = entity
    
    if entity.respond_to?("register_as_sight_listener") then
      #puts "Registering sight listener"
      entity.register_as_sight_listener(get_sphere_of_influence(loc, entity.sight_range))
    end
    
    if (entity.respond_to?("register_as_sound_listener")) then
      #puts "Registering sound listener"
      entity.register_as_sight_listener(get_sphere_of_influence(loc, entity.sound_range))
    end
    
    if (entity.respond_to?("register_as_smell_listener")) then
      #puts "Registering smell listener"
      entity.register_as_smell_listener(get_sphere_of_influence(loc, entity.smell_range))
    end
    
    if (entity.respond_to?("register_as_touch_listener")) then
      #puts "Registering touch listener"
      entity.register_as_touch_listener(get_sphere_of_influence(loc, entity.touch_range))
    end
    
    if (entity.respond_to?("register_as_sense_listener")) then
      #puts "Registering sense listener"
      entity.register_as_sense_listener(get_sphere_of_influence(loc, entity.sense_range))
    end
    
  end
  
  def add_item(loc, item)
    get_grid(loc).items + [item]
  end
  
  def add_items(loc, items)
    get_grid(loc).items + items
  end
  
  def move_entity(entity, loc_to) 
    puts "Moving from #{entity.loc.to_s} to #{loc_to.to_s}"
    grid_from = get_grid(entity.loc)
    grid_to = get_grid(loc_to)
    
    if grid_from != nil && grid_to != nil then
      success = grid_to.receive_entity(entity)
      
      if success then
        entity.loc = loc_to
        grid_to.entity = entity
        grid_from.entity = nil
      end
    end
  end
  
  def to_s
    @grid.each {|x| x.each {|y| puts y.to_s}}
  end
  
  def render
    x = 0
    y = 0
    
    while y < @height 
      @grid[x][y].render

      if y < @height then
        if x < @width - 1 then
          x = x + 1
        else
          x = 0
          y = y + 1
          print "\n"
        end
      end
    end   
  end
  
  private
  
  # Returns the grids that fall within range of the location
  def get_sphere_of_influence(loc, range)
    grids = []
    y_neg_range = loc.point_y - range < 0 ? loc.point_y : range
    y_pos_range = loc.point_y + range > @width ? loc.point_y : range
    x_neg_range = loc.point_x - range < 0 ? loc.point_x : range
    x_pos_range = loc.point_x + range > @width ? loc.point_x : range

    if range > 0 then
      for y in -y_neg_range...y_pos_range + 1
        if y.abs != range then
          for x in -(x_neg_range-y.abs)...x_pos_range + 1 - y.abs
            #puts "(#{x},#{y}) translated (#{loc.point_x + x},#{loc.point_y + y}) range = #{x.abs+y.abs}  #{get_grid(Location.new(loc.point_x + x,loc.point_y + y)).to_s}"
            grids = add_grid(grids,get_grid(Location.new(loc.point_x + x,loc.point_y + y)))
          end
        else
          #puts "(#{0},#{y}) translated (#{loc.point_x}, #{loc.point_y + y}) range = #{0+y.abs}  #{get_grid(Location.new(loc.point_x, loc.point_y + y))}"
          grids = add_grid(grids,get_grid(Location.new(loc.point_x, loc.point_y + y)))
        end
      end
    else
      grids[0] = get_grid(loc)
    end
    
    return grids
  end
  
  def add_grid(grid_list, grid) 
    if grid != nil then
      return grid_list + [grid];
    else
      return grid_list
    end
  end
end

class Location
  
  attr_reader :point_x, :point_y
  
  def initialize(point_x, point_y)
    @point_x = point_x
    @point_y = point_y
  end
  
  def up_left
   return Location.new(@point_x - 1, @point_y - 1)
  end
  
  def up
    return Location.new(@point_x, @point_y - 1)
  end
  
  def up_right
    return Location.new(@point_x + 1, @point_y - 1)
  end
  
  def left
    return Location.new(@point_x - 1, @point_y)
  end
  
  def right
    return Location.new(@point_x + 1, @point_y)
  end
  
  def down_left
    return Location.new(@point_x - 1, @point_y + 1)
  end
  
  def down
    return Location.new(@point_x, @point_y + 1)
  end
  
  def down_right
    return Location.new(@point_x + 1, @point_y + 1)
  end
  
  def to_s
    return "loc(#{point_x},#{point_y})"
  end
end

class Grid
  include GridObserver
  
  attr_accessor :loc, :entity, :structure, :items
  
  def initialize(loc, structure, entity, items)
    super()
    @loc = loc
    @structure = structure
    @entity = entity
    @items = items
  end
  
  def receive_entity(entity)
    @entity = entity
  end
  
  def receive_event(grid_event) 
    #puts "Received #{grid_event.type} event"
    case grid_event.type
    when GridEvent::SIGHT
      notify_sight_listeners(grid_event)
    when GridEvent::SOUND
      notify_sound_listeners(grid_event)
    when GridEvent::SMELL
      notify_smell_listeners(grid_event)
    when GridEvent::TOUCH
      notify_touch_listeners(grid_event) 
    when GridEvent::SENSE
      notify_sense_listeners(grid_event)
    end
  end
  
  def to_s
    s = "#{loc.to_s} | #{structure.to_s} | #{entity.to_s}"
    
    if items != nil then
      s + "| #{items.each {|item| item.to_s}}"
    end
    
    return s
  end
  
  def render
    if @entity != nil then
      @entity.render
    elsif @items != nil && @items[0] != nil then
      @items[0].render
    else
      @structure.render
    end
  end
end

class Renderable
  
  attr_accessor :render, :color
  
  def initialize(render, color)
    @render = render
    @color = color
  end
  
  def render
    print @render
  end
end

class Structure < Renderable
  def to_s
    return "structure(test)"
  end
end

class Floor < Structure
  def initialize
    super(".", nil)
  end
end

class Entity < Renderable
  
  attr_accessor :name, :loc, :sight_range, :sound_range, :smell_range, :touch_range, :sense_range
  
  def initialize(render, color, name, loc, sight_range, sound_range, smell_range, touch_range, sense_range)
    super(render, color)
    @name = name
    @loc = loc
    @sight_range = sight_range
    @sound_range = sound_range
    @smell_range = smell_range
    @touch_range = touch_range
    @sense_range = sense_range
  end
  
  def to_s
    return "entity(#{name},#{sight_range},#{sound_range},#{smell_range},#{touch_range},#{sense_range})"
  end
end

class Item < Renderable
  def to_s
    return "item(test)"
  end
end

class AcidBlob < Entity
  include TouchListener, SenseListener
  
  def initialize(name, loc, sight_range, sound_range, smell_range, touch_range, sense_range)
    super("a", nil, name, loc, sight_range, sound_range, smell_range, touch_range, sense_range)
  end
  
  protected
  
  def touch_event(event)
    puts "You have been #{event} by the acid blob"
  end
  
  def sense_event(event)
    puts "You have been #{event} by the acid blob"
  end
end

class Player < Entity
  include SightListener, SoundListener, SmellListener, TouchListener, SenseListener 
  
  def initialize(name, loc, sight_range, sound_range, smell_range, touch_range, sense_range)
    super("@", nil, name, loc, sight_range, sound_range, smell_range, touch_range, sense_range) 
  end
end

m = Map.new(10,6)
s = Location.new(0,0)
e = Location.new(5,5)
a = AcidBlob.new("Acid Blob",e,0,0,0,0,1)
p = Player.new("Matt",s,0,0,0,0,0)
m.add_entity(e, a)
m.add_entity(s, p)
m.render

while (input = gets.chomp!) != "q"
  # Movement yuhjklbn
  case input
  when "y"
    m.move_entity(p, p.loc.up_left)
  when "u"
    m.move_entity(p, p.loc.up_right)
  when "h"
    m.move_entity(p, p.loc.left)
  when "j"
    m.move_entity(p, p.loc.down)
  when "k"
    m.move_entity(p, p.loc.up)
  when "l"
    m.move_entity(p, p.loc.right)
  when "b"
    m.move_entity(p, p.loc.down_left)
  when "n"
    m.move_entity(p, p.loc.down_right)
  end
  
  m.render
end
#m.get_grid(Location.new(5,4)).receive_event(GridEvent.new(GridEvent::SIGHT))
